' //***************************************************************************
' // ***** Script Header *****
' //
' // Solution:  Solution Accelerator for Business Desktop Deployment
' // File:      ZTIUtility.vbs
' //
' // Purpose:   Utility functions used by all other ZTI scripts
' //
' // Usage:     <script language="VBScript" src="ZTIUtility.vbs"/>
' //
' // Microsoft Solution Version:  4.2.684
' // Microsoft Script Version:    4.2.684
' // Customer Build Version:      1.0.0
' // Customer Script Version:     1.0.0
' //
' // Microsoft History:
' // 2.6.0   MTN  01/01/2006  Modified FindFile logic to work for USMT executables
' // 3.0.2   MTN  03/30/2006  Added SMSv4 task sequencer logic
' // 3.0.60  MTN  08/28/2006  Added FindMappedDrive.
' // 3.0.78  MTN  10/10/2006  Added BDDUtility and SetBuildProperties functions.
' // 3.0.88  MTN  10/21/2006  Merged in database changes from ZTIGather.wsf.
' // 3.0.128 MTN  12/06/2006  Added new parameter to ShowActionProgress.
' // 3.0.129 MTN  12/08/2006  Added GetDAT and SetDAT to simplify manipulation
' //                          of VARIABLES.DAT.
' // 3.0.130 MTN  12/13/2006  Disable connection pooling for ADO connections.
' // 3.0.136 MTN  01/08/2007  Make sure a connection is established to the
' //                          EventShare.
' // 3.0.142 MTN  02/06/2007  Fixed typo in SectionName property.
' // 3.0.142 MTN  04/17/2007  Fixed Database logic to find custom INI files.
' // 4.0.175 MTN  05/04/2007  Renamed SetBuildProperties to SetTaskSequenceProperties.
' // 4.0.222 MTN  06/26/2007  Changed LocalRootPath to account for SCCM scenarios.
' // 4.0.227 TJ   06/27/2007  Updated InstallPath to use new NormalizePath function.
' // 4.0.229 TPM  06/29/2007  Added SCCM logic to ValidateConnection to populate UserID, UserDomain, Password
' // 4.0.231 MTN  07/03/2007  Changed to use three-digit list suffixes
' // 4.0.241 MTN  07/20/2007  Added web service logic.
' // 4.0.252 MTN  07/31/2007  Added IsSupportedPlatform method
' // 4.0.254 TPM  08/01/2007  Changed FindFile to return CurrentDirectory if file is found in .\
' // 4.0.306 MTN  08/15/2007  Set port in SQL connect string even if no instance
' //                          is specified.
' // 4.0.322      09/04/2007  Updated SupportedPlatform logic to correctly process PE env.
' // 4.0.365      10/18/2007  Removed database caching logic.
' // 4.0.379 MTN  11/02/2007  Modified log copying logic to create a per-computer folder and to copy
' //                          SMSTS.LOG.
' // 4.2.571 TJ   02/10/2008  Added support for base64 encode/decode (Utility class)
' // 4.2.571 MTN  05/07/2008  Modified event logic for management pack.
' // 4.2.684 MTN  05/31/2008  Changed the ComputerName utility function to also look at
' //                          the _SMSTSMachineName variable.  Modified the CopyLog function to
' //                          use the ComputerName function.
' //
' // Customer History:
' //
' // ***** End Header *****
' //***************************************************************************


Option Explicit

' Public constants

Public Const ForReading = 1
Public Const ForWriting = 2
Public Const ForAppending = 8

Public Const Success = 0
Public Const Failure = 1

Public Const LogTypeInfo = 1
Public Const LogTypeWarning = 2
Public Const LogTypeError = 3

Public Const TextCompare = 1

Public Const adOpenStatic = 3
Public Const adLockReadOnly = 1
Public Const adLockOptimistic = 3

Const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

' Global variables

Dim oShell, oEnv, oNetwork, oFSO, objWMI, oDrive
Dim oUtility, oLogging, oEnvironment


' Initialization code

On Error Resume Next
Set oUtility = New Utility
oUtility.PrepareEnvironment

function PrnErrValue ( iError )
    if abs(iError) >= 1000000 then
        PrnErrValue = cstr(iError) & "  0x" & right( "00000000" & hex ( iError ), 8 )
    else
        PrnErrValue = cstr(iError) 
    end if
end function

Sub ProcessResults(iRc)

	Dim iMainRc
	Dim sError
	Dim sMainRc

	iMainRc = iRc
	sMainRc = PrnErrValue ( iMainRc )
	If Err then
		iMainRc = Err.Number
		sError = Err.Description
		sMainRc = PrnErrValue ( iMainRc )
		oLogging.CreateEvent 41002, LogTypeError, "ZTI ERROR - Unhandled error returned by " & oUtility.ScriptName & ": " & sError & " (" & sMainRc & ")", Array(iMainRc)
	ElseIf iRc <> Success then
		oLogging.CreateEvent 41002, LogTypeError, "ZTI ERROR - Non-zero return code by " & oUtility.ScriptName & ", rc = " & sMainRc, Array(iMainRc)
	Else
		oLogging.CreateEvent 41001, LogTypeInfo, oUtility.ScriptName & " processing completed successfully.", Array()
	End if
	WScript.Quit iMainRc

End Sub

' Public classes

class Logging


	' ***  Public variables ***

	Public LogFile
	Public MasterLogFile
	Public Component
	Public Debug


	' ***  Private variables ***


	' ***  Constructor and destructor ***

	Private Sub Class_Initialize


		' Set file names and paths

		Component = oUtility.ScriptName
        	LogFile = Component & ".log"
		MasterLogFile = "BDD.log"


		' Set debug to false, allow PrepareEnvironment to override

		Debug = False


		' Log where the SMSTS.LOG can be found

		If oEnvironment.Item("_SMSTSLogPath") <> "" then
			CreateEntry "The task sequencer log is located at " & oEnvironment.Item("_SMSTSLogPath") & "\SMSTS.LOG.  For task sequence failures, please consult this log.", LogTypeInfo
		End if

	End Sub


	' ***  Public methods ***

	Public Function CreateEntry(sLogMsg, iType)
		Dim sTime, sDate, sTempMsg, oLog, oConsole


		On Error Resume Next


		' Suppress messages containing password

		If not Debug then
			If Instr(1, sLogMsg, "password", 1) > 0 then
				sLogMsg = "<Message containing password has been suppressed>"
			End if
		End if


		' Populate the variables to log

		sTime = Right("0" & Hour(Now), 2) & ":" & Right("0" & Minute(Now), 2) & ":" & Right("0" & Second(Now), 2) & ".000+000"
		sDate = Right("0"& Month(Now), 2) & "-" & Right("0" & Day(Now), 2) & "-" & Year(Now)
		sTempMsg = "<![LOG[" & sLogMsg & "]LOG]!><time=""" & sTime & """ date=""" & sDate & """ component=""" & Component & """ context="""" type=""" & iType & """ thread="""" file=""" & oUtility.ScriptName & """>"


		' Make sure the LogPath directory exists

		oUtility.VerifyPathExists LogPath


		' If debug, echo the message

		If Debug then
			Set oConsole = oFSO.GetStandardStream(1) 
			oConsole.WriteLine sLogMsg
		End if


		' Create the log entry

		Err.Clear
		Set oLog = oFSO.OpenTextFile(LogPath & "\" & LogFile, ForAppending, True)
		If Err then
			Err.Clear
			Exit Function
		End if
		oLog.WriteLine sTempMsg
		oLog.Close


		' Create the master log entry

		Set oLog = oFSO.OpenTextFile(LogPath & "\" & MasterLogFile, ForAppending, True)
		If Err then
			Err.Clear
			Exit Function
		End if
		oLog.WriteLine sTempMsg
		oLog.Close


		Err.Clear

	End Function


	Function CreateEvent(iEventID, iType, sMessage, arrParms)

		Dim re
		Dim sEventFile
		Dim fptr
		Dim sLine
		Dim iMOMType
		Dim sDomain
		Dim sComputer
		Dim sPackage
		Dim sAdvert
		Dim i
		Dim sCurrentStep
		Dim sTotalSteps


		On Error Resume Next

		Set re = New RegExp


		' Log the message specified

		CreateEntry sMessage, iType


		' Make sure an event share was specified

		If not oEnvironment.Item("EventShare") <> "" then
			Exit Function
		End if


		' Make sure the path is accessible

		oUtility.ValidateConnection oEnvironment.Item("EventShare")


		' Create a unique file name on the share.

		sEventFile = oEnvironment.Item("EventShare") & "\" & oEnvironment.Item("Hostname") & "_" & oFSO.GetTempName
		Set fptr = oFSO.CreateTextFile(sEventFile, True)
		If Err then
			CreateEntry "Unable to create event file " & sEventFile & ": " & Err.Description & " (" & Err.Number & ")", LogTypeError
		End if


		' Build the first line to write

		Select Case iType
		Case LogTypeInfo
			iMOMType = 4
		Case LogTypeWarning
			iMOMType = 2
		Case LogTypeError
			iMOMType = 1
		End Select

		sDomain = oEnvironment.Item("JoinDomain")
		sComputer = oUtility.ComputerName

		If oEnvironment.Item("_SMSTSAdvertID") <> "" then
			sAdvert = oEnvironment.Item("_SMSTSAdvertId")
			sPackage = oEnvironment.Item("_SMSTSPackageID")
		ElseIf oEnvironment.Item("OSDADVERTID") = "" then
			sAdvert = "OSD00000"
			sPackage = "OSD00000"
		Else
			sAdvert = oEnvironment.Item("OSDADVERTID")
			sPackage = oEnvironment.Item("OSDPACKAGEID")
		End if

		sCurrentStep = oEnvironment.Item("_SMSTSNextInstructionPointer")
		sTotalSteps = oEnvironment.Item("_SMSTSInstructionTableSize")

		' Write out the first line (common content)

		sLine = CStr(iEventID) & "," & CStr(iMOMType) & "," & sDomain & "," & sComputer & "," & sPackage & "," & sAdvert & "," & sCurrentStep & "," & sTotalSteps & "," & oEnvironment.Item("DeploymentMethod")
		fptr.WriteLine sLine


		' Write out the second line (insertion strings)

		fptr.WriteLine Join(arrParms, ",")


		' Write out the third line (message)

		fptr.WriteLine sMessage


		' Close the file

		fptr.Close
		Set fptr = Nothing

		CreateEntry "Event " & CStr(iEventID) & " sent: " & sMessage, LogTypeInfo


	End Function



	Function GetDiscoveryArray

		Dim sLine
		Dim dicMac
		Dim arrMac
		Dim i


		' Add the local entries

		sLine = oEnvironment.Item("PHASE") & "," & oEnvironment.Item("AssetTag") & "," & oEnvironment.Item("UUID")
		Set dicMac = oEnvironment.ListItem("MacAddress")
		arrMac = dicMac.Keys

		For i = 0 to 4
			If i > UBound(arrMac) then
				sLine = sLine & ","
			Else
				sLine = sLine & "," & arrMac(i)
			End if
		Next


		' Add the user data entries (which might not be available yet)

		sLine = oEnvironment.Substitute(sLine & "," & oEnvironment.Item("SLShare") & "," & oEnvironment.Item("UDShare") & "," & oEnvironment.Item("UDDir"))


		' Split it into an array and return it

		GetDiscoveryArray = Split(sLine,",")

	End Function


	Public Function CopyLog()

		Dim iRetVal, fptr1, fptr2, sLine, sNewLogFolderName, sLogFile
		Dim sComputer


		On Error Resume Next


		' Figure out where to copy the local logfile to

		If oEnvironment.Item("SLShare") = "" then
			oLogging.CreateEntry "Unable to copy log to the network as no SLShare value was specified.", LogTypeInfo
			Exit Function
		End if


		' Make sure the path is accessible

		oUtility.ValidateConnection oEnvironment.Item("SLShare")
		oUtility.VerifyPathExists oEnvironment.Item("SLShare")
		If not oFSO.FolderExists(oEnvironment.Item("SLShare")) then
			oLogging.CreateEntry "An invalid SLShare value of " & oEnvironment.Item("SLShare") & " was specified.", LogTypeWarning
			Exit Function
		End if


		' Figure out the computer name

		sComputer = oUtility.ComputerName


		' Construct the new folder name

		sNewLogFolderName = oEnvironment.Item("SLShare") & "\" & sComputer
		oUtility.VerifyPathExists sNewLogFolderName


		' Copy the SMSTS.LOG if present

		If oFSO.FileExists(oEnvironment.Item("_SMSTSLogPath") & "\SMSTS.LOG") then
			oLogging.CreateEntry "Copying " & oEnvironment.Item("_SMSTSLogPath") & "\SMSTS.LOG to " & sNewLogFolderName & "\SMSTS.LOG", LogTypeInfo
			oFSO.CopyFile oEnvironment.Item("_SMSTSLogPath") & "\SMSTS.LOG", sNewLogFolderName & "\", True
		End if


		' Make sure we have a local log file; it might not exist if the disk isn't yet writable.

		sLogFile = LogPath & "\" & MasterLogFile
		If not oFSO.FileExists(sLogFile) then
			oLogging.CreateEntry "Master log file " & sLogFile & " was not found, unable to copy to " & sNewLogFolderName & "\BDD.LOG", LogTypeInfo
			Exit Function
		End if


		' Copy the file contents to the end of the network file. (It might already exist from a previous action, so append to it.)

		oLogging.CreateEntry "Copying log " & sLogFile & " contents to " & sNewLogFolderName & "\BDD.LOG", LogTypeInfo

		Set fptr1 = oFSO.OpenTextFile(sLogFile, ForReading, True)
		If Err then
			oLogging.CreateEntry "Unable to open " & sLogFile & " for reading: " & Err.Description & " (" & Err.Number & ")", LogTypeInfo
			Err.Clear
			Exit Function
		End if

		Set fptr2 = oFSO.OpenTextFile(sNewLogFolderName & "\BDD.LOG", ForAppending, True)
		If Err then
			oLogging.CreateEntry "Unable to open " & sNewLogFolderName & "\BDD.LOG for appending: " & Err.Description & " (" & Err.Number & ")", LogTypeInfo
			Err.Clear
			Exit Function
		End if

		Do while Not fptr1.AtEndOfStream
			sLine = fptr1.readline
			fptr2.writeline sLine
		Loop

		fptr1.Close
		fptr2.Close


		Err.Clear
		On Error Goto 0

	End Function


	Public Function ReportProgress(sMsg, iPercent)

		Dim iMaxPercent
		Dim oProgress
		Dim uStep
		Dim uMaxStep


		' Try to create the progress UI object
		
		On Error Resume Next
		Set oProgress = CreateObject("Microsoft.SMS.TSProgressUI")
		If Err then
			Err.Clear
			Exit Function
		End if
		On Error Goto 0


		' Update the progress

		On Error Resume Next

		iMaxPercent = 100
		uStep = CLng(oEnvironment.Item("_SMSTSNextInstructionPointer"))
		uMaxStep = CLng(oEnvironment.Item("_SMSTSInstructionTableSize"))
		Call oProgress.ShowActionProgress(oEnvironment.Item("_SMSTSOrgName"), oEnvironment.Item("_SMSTSPackageName"), oEnvironment.Item("_SMSTSCustomProgressDialogMessage"), oEnvironment.Item("_SMSTSCurrentActionName"), (uStep), (uMaxStep), sMsg, (iPercent), (iMaxPercent))
		If Err then
			CreateEntry "Unable to update progress: " & Err.Description & " (" & Err.Number & ")", LogTypeInfo
			ReportProgress = Failure
			Err.Clear
			Exit Function
		End if

		On Error Goto 0


		' Dispose of the object

		Set oProgress = Nothing

	End Function


	Property Get LogPath
		LogPath = oUtility.LogPath
	End Property

End Class


Class Environment

	' ***  Public variables ***

	Public PersistFile


	' ***  Private variables ***

	Private oVariables
	Private osdV3
	Private osdV4


	' ***  Constructor and destructor ***

	Private Sub Class_Initialize

		PersistFile = "VARIABLES.DAT"


		On Error Resume Next
		Err.Clear
		Set oVariables = oUtility.CreateXMLDOMObject
		If Err then
			' Unable to create XML object
			Err.Clear
		End if
		On Error Goto 0


		' Create SMS 2003 OSD environment instance

		On Error Resume Next
		Err.Clear
		Set osdV3 = CreateObject("OSD.Environment")
		If Err then
			Set osdV3 = Nothing
			Err.Clear
		End if
		On Error Goto 0


		' Create SMSv4 Task Sequence environment

		On Error Resume Next
		Err.Clear
		Set osdV4 = CreateObject("Microsoft.SMS.TSEnvironment")
		If Err then
			Set osdV4 = Nothing
			Err.Clear
		End if
		On Error Goto 0

		Err.Clear

	End Sub


	' ***  Private methods ***

	Function GetOSDV3(sVariable)

		GetOSDV3 = ""
		On Error Resume Next
		If osdV3 is Nothing then
			Exit Function
		Else
			GetOSDV3 = osdV3(sVariable)
			If Err then
				' oLogging.CreateEntry "WARNING - Unable to get SMS 2003 OSD environment: " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
			End if
		End if
		On Error Goto 0
		Err.Clear

	End Function

	Sub SetOSDV3(sVariable, sNew)

		On Error Resume Next
		If osdV3 is Nothing then
			Exit Sub
		Else
			osdV3(sVariable) = sNew
			If Err then
				' oLogging.CreateEntry "WARNING - Unable to set SMS 2003 OSD environment: " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
			End if
		End if
		On Error Goto 0
		Err.Clear

	End Sub

	Function GetOSDV4(sVariable)

		GetOSDV4 = ""
		On Error Resume Next
		If osdV4 is Nothing then
			Exit Function
		Else
			GetOSDV4 = osdV4(sVariable)
			If Err then
				' oLogging.CreateEntry "WARNING - Unable to get SMSv4 Task Sequencer environment: " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
			End if
		End if
		On Error Goto 0
		Err.Clear

	End Function

	Sub SetOSDV4(sVariable, sNew)

		On Error Resume Next
		If osdV4 is Nothing then
			Exit Sub
		Else
			osdV4(sVariable) = sNew
			If Err then
				' oLogging.CreateEntry "WARNING - Unable to get SMSv4 Task Sequencer environment: " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
			End if
		End if
		On Error Goto 0
		Err.Clear

	End Sub

	Function GetDAT(sVariable)
		Dim oNode

		GetDAT = ""
		If oFSO.FileExists(PersistPath & "\" & PersistFile) then

			oVariables.Load PersistPath & "\" & PersistFile
			Set oNode = oVariables.DocumentElement.SelectSingleNode("//var[@name='" & UCase(sVariable) & "']")
			If not (oNode is Nothing) then
				GetDAT = oNode.Text
			End if


			' Try to set the value in the SMS environments (sync)

			If GetDAT <> "" then
				SetOSDV4 sVariable, GetDAT
				SetOSDV3 sVariable, GetDAT
			End if

		End if

	End Function

	Sub SetDAT(sVariable, sNew)
		Dim oNode

		If oFSO.FolderExists(PersistPath) then

			If oFSO.FileExists(PersistPath & "\" & PersistFile) then
				oVariables.Load PersistPath & "\" & PersistFile
			Else
				oVariables.LoadXml "<?xml version=""1.0"" ?><MediaVarList Version=""4.00.5345.0000""></MediaVarList>"
			End if

			Set oNode = oVariables.DocumentElement.SelectSingleNode("//var[@name='" & UCase(sVariable) & "']")
			If oNode is Nothing then
				Set oNode = oVariables.CreateElement("var")
				oVariables.DocumentElement.appendChild oNode
			End if
			oNode.SetAttribute "name", UCase(sVariable)
			oNode.Text = sNew

			On Error Resume Next
			oVariables.Save PersistPath & "\" & PersistFile
			If Err then
				oLogging.CreateEntry "WARNING - Unable to persist items to " & PersistPath & "\" & PersistFile & ": " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
				Err.Clear
			End if
			On Error Goto 0

		End if

	End Sub


	' ***  Public methods ***

	Public Property Get Exists(sVariable)

		If Item(sVariable) <> "" then
			Exists = True
		Else
			Exists = False
		End if

	End Property

	Public Property Get Item(sVariable)

		' First try SMSv4, then SMS 2003, then XML file

		Item = GetOSDV4(sVariable)

		If Item = "" then
			Item = GetOSDV3(sVariable)

			' Try to set the value in the SMSv4 environment (sync)

			If Item <> "" then
				SetOSDV4 sVariable, Item
			End if

		End if

		If Item = "" then
			Item = GetDAT(sVariable)

			' Try to set the value in the SMS environments (sync)

			If Item <> "" then
				SetOSDV4 sVariable, Item
				SetOSDV3 sVariable, Item
			End if

		End if

	End Property


	Public Property Let Item(sVariable, sNew)

		' Save to all available environments

		SetOSDV4 sVariable, sNew
		SetOSDV3 sVariable, sNew
		SetDAT sVariable, sNew


		' For completeness, set the variable in the process's environment as well

		oEnv(sVariable) = sNew

		oLogging.CreateEntry "Property " & sVariable & " is now = " & sNew, LogTypeInfo

	End Property

	Public Property Get ListItem(sVariable)

		Dim i
		Dim sPadded

		Set ListItem = CreateObject("Scripting.Dictionary")
		For i = 1 to 999

			sPadded = sVariable & Right("000" & CStr(i), 3)
			If Item(sPadded) <> "" then
				If not ListItem.Exists(Item(sPadded)) then
					ListItem.Add Item(sPadded), ""
				End if
			ElseIf Item(sVariable & CStr(i)) <> "" then
				If not ListItem.Exists(Item(sVariable & CStr(i))) then
					ListItem.Add Item(sVariable & CStr(i)), ""
				End if
			Else
				Exit For  ' Exit on first "not found" entry
			End if

		Next

	End Property
	
	Public Sub SetListItemEx (sVariable, sNew)
	
		Dim sElement
		Dim i
		Dim sPadded

		i = 0
		For each sElement in sNew
			i = i + 1
			sPadded = sVariable & Right("000" & CStr(i), 3)
			Item(sPadded) = sElement
		Next


		' Blank out the next in case there was something there		

		sPadded = sVariable & Right("000" & CStr(i+1), 3)
		If Exists(sPadded) then
			Item(sPadded) = ""
		End if


		' Blank out the non-list item if it was there

		If exists(sVariable) then
			Item(sVariable) = ""
		End if

	End sub

	Public Property Set ListItem(sVariable, sNew)
	
	    SetListItemEx sVariable, sNew

	End Property
	
	Public Property Let ListItem(sVariable, sNew)

	    SetListItemEx sVariable, sNew

	End Property


	Function Substitute(sVal)

		Dim sElement, re, sPattern, sReplace, tmpArray, iPos, iEnd, sEval

		Set re = New RegExp
		re.IgnoreCase = True
		re.Global = True


		' Substitute the appropriate values

		iPos = Instr(sVal, "%")
		While iPos > 0

			' Find ending "%"
			iEnd = Instr(iPos+1, sVal, "%")
			If iEnd > 0 then

				sEval = Mid(sVal, iPos+1, iEnd - iPos - 1)

				sReplace = ""
				If oEnvironment.ListItem(sEval).Count > 0 then
					For each sReplace in oEnvironment.ListItem(sEval).Keys
						Exit For  ' Grab the first value
					Next
				ElseIf oEnvironment.Item(sEval) <> "" then
					sReplace = oEnvironment.Item(sEval)
				End if

				If sReplace <> "" then
					If iPos = 1 then
						sVal = CStr(sReplace) & Mid(sVal, iEnd + 1)
					ElseIf iEnd = Len(sVal) then
						sVal = Left(sVal, iPos - 1) & CStr(sReplace)
					Else
						sVal = Left(sVal, iPos - 1) & CStr(sReplace) & Mid(sVal, iEnd + 1)
					End if
					iPos = Instr(sVal, "%")
				Else
					iPos = iEnd
				End if
			Else
				iPos = iEnd
		 	End if
		WEnd


		' Expand any environment variables

		sVal = oShell.ExpandEnvironmentStrings(sVal)


		' Finally, look for evaluate blocks

		iPos = Instr(sVal, "#")
		While iPos > 0

			' Find ending "#"
			iEnd = Instr(iPos+1, sVal, "#")
			If iEnd > 0 then
				sEval = Mid(sVal, iPos+1, iEnd - iPos - 1)
				On Error Resume Next
				sReplace = Eval(sEval)
				On Error Goto 0
				If iPos = 1 then
					sVal = sReplace & Mid(sVal, iEnd + 1)
				ElseIf iEnd = Len(sVal) then
					sVal = Left(sVal, iPos - 1) & sReplace
				Else
					sVal = Left(sVal, iPos - 1) & sReplace & Mid(sVal, iEnd + 1)
				End if
				iPos = Instr(sVal, "#")
			 Else
				iPos = iEnd
			 End if

		WEnd

		Substitute = Trim(sVal)

	End Function


	Public Property Get PersistPath

		PersistPath = oUtility.LogPath

	End Property


	Public Function Release

		Set osdV3 = Nothing
		Set osdV4 = Nothing

	End Function

End Class


Class Utility

	' ***  Properties ***

	Public Arguments


	' ***  Private variables ***

	Dim dicNetworkConnections
	Dim sScriptDir
	Dim oBDDUtility
	Dim oSupportedPlatforms


	' ***  Constructor and destructor ***

	Private Sub Class_Initialize

		Dim re
		Dim arrDrives, i


		' Initialize the objects

		Set oFSO = CreateObject("Scripting.FileSystemObject")
		Set oShell = CreateObject("WScript.Shell")
		Set oEnv = oShell.Environment("PROCESS")
		Set oNetwork = CreateObject("WScript.Network")
		Set objWMI = Nothing
		On Error Resume Next
		Set objWMI = GetObject("winmgmts:")
		On Error Goto 0
		Set dicNetworkConnections = CreateObject("Scripting.Dictionary")
		dicNetworkConnections.CompareMode = TextCompare
		Set oBDDUtility = Nothing
		Set oSupportedPlatforms = Nothing


		' Process the parameters

		On Error Resume Next

		Set Arguments = WScript.Arguments.Named

		On Error Goto 0


		' Get the script directory

	    On Error Resume Next
	        sScriptDir = empty
	        sScriptDir = oFSO.GetParentFolderName(WScript.ScriptFullName)
		    
	        if isempty(sScriptDir) then
                if window.location.hostname <> "" then
                    sScriptDir = oFSO.GetParentFolderName( unescape("\\" & window.location.hostname & window.location.pathname ) )
                else
                    sScriptDir = oFSO.GetParentFolderName( unescape(window.location.pathname) )
                end if 
	        end if 
	    On Error Goto 0
		    
		If Mid(sScriptDir, 2, 2) = ":\" then


			' Look to see if this is a mapped drive
 
			On Error Resume Next
			Set arrDrives = oNetwork.EnumNetworkDrives
			If Err then
				On Error Goto 0
				oLogging.CreateEntry "ERROR - Network is unavailable: " & Err.Description & " (" & Err.Number & ")", LogTypeError
			Else
				On Error Goto 0
				For i = 0 to arrDrives.Count - 1 Step 2
					If arrDrives.Item(i) = UCase(Left(sScriptDir,2)) then
						If Len(sScriptDir) > 3 then
							sScriptDir = arrDrives.Item(i+1) & Mid(sScriptDir, 3)
						Else
							sScriptDir = arrDrives.Item(i+1)
						End if
						Exit For
					End if
				Next
			End if
			On Error Goto 0

		End if

	End Sub


	' ***  Public methods ***

	Public Sub PrepareEnvironment

		Dim sArg

		Set oEnvironment = New Environment
		Set oLogging = New Logging


		' Loop through all the parameters and turn them into environment variables.  Enforce debug values.

		On Error Resume Next

		For each sArg in Arguments
			If UCase(sArg) = "DEBUG" then
				If UCase(Arguments(sArg)) = "TRUE" or UCase(Arguments(sArg)) = "FALSE" then
					oLogging.CreateEntry "'debug' parameter was specified.", LogTypeInfo
				Else
					oLogging.CreateEntry "Invalid 'debug' parameter specified: " & Arguments(sArg), LogTypeError
					WScript.Quit Failure
				End if
				oEnvironment.Item(sArg) = UCase(Arguments(sArg))		
			Else
				oEnvironment.Item(sArg) = Arguments(sArg)
			End if
		Next

		On Error Goto 0
		Err.Clear


		' Set a default for debug (if necessary)

		If oEnvironment.Item("Debug") = "" then
			oEnvironment.Item("Debug") = "FALSE"
		End if


		If UCase(oEnvironment.Item("Debug")) = "TRUE" then
			oLogging.Debug = True
		Else
			oLogging.Debug = False
		End if

	End Sub


	Property Get LocalRootPath

		' If in SCCM, use their path.  Otherwise, determine what path we should use.

		If oEnvironment.GetOSDV4("_SMSTSBootImageID") <> "" then
			LocalRootPath = oEnvironment.GetOSDV4("_SMSTSMDataPath")
		ElseIf oEnv("SystemDrive") = "X:" then   ' We're in PE
			If oFSO.GetDrive("X:").DriveType <> 2 then  ' We're definitely not running from a disk
				LocalRootPath = "C:\MININT"
			Else  ' We might be running from a disk

				If Instr(BootDevice, "RAMDISK") > 0 then  ' We're running in a PE 2.0 RAMdisk
					If oFSO.FolderExists("C:\") then
						LocalRootPath = "C:\MININT"
					Else
						LocalRootPath = "X:\MININT"
					End if

				Else   ' We're running from the disk
					LocalRootPath = "X:\MININT"
				End if
			End if
		Else  ' We're in a full OS
			LocalRootPath = oEnv("SystemDrive") & "\MININT"
		End if

		oUtility.VerifyPathExists LocalRootPath
				
	End Property

	Property Get BootDevice

		On Error Resume Next
		Err.Clear
		BootDevice = UCase(oShell.RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SystemBootDevice"))
		If Err then
			BootDevice = ""
		End if
		On Error Resume Next

	End Property


	Property Get LogPath

		If oEnvironment.GetOSDV4("_SMSTSBootImageID") <> "" then
			LogPath = oEnvironment.GetOSDV4("_SMSTSLogPath")
		Else
			LogPath = LocalRootPath & "\SMSOSD\OSDLOGS"
		End if
		oUtility.VerifyPathExists LogPath

	End Property


	Property Get StatePath

		StatePath = LocalRootPath & "\StateStore"
		oUtility.VerifyPathExists StatePath

	End Property


	Property Get ScriptName

		On Error Resume Next
		ScriptName = oFSO.GetBaseName(Wscript.ScriptName)
		If Err then
			ScriptName = oFSO.GetBaseName(Unescape(window.location.pathname))
		End if
		On Error Goto 0

	End Property


	Property Get ScriptDir

		ScriptDir = sScriptDir

	End Property


	Public Function ReadIni(file, section, item)

		Dim line, equalpos, leftstring, ini

		ReadIni = ""
		file = Trim(file)
		item = Trim(item)

		On Error Resume Next
		Set ini = oFSO.OpenTextFile( file, 1, False)
		If Err then
			Err.Clear
			Exit Function
		End if
		On Error Goto 0

		Do While (not ini.AtEndOfStream)
			line = ini.ReadLine
			line = Trim(line)
			If LCase(line) = "[" & LCase(section) & "]" and (not ini.AtEndOfStream) Then
				line = ini.ReadLine
				line = Trim(line)
				Do While Left( line, 1) <> "["
					'If InStr( 1, line, item & "=", 1) = 1 Then
					equalpos = InStr(1, line, "=", 1 )
					If equalpos > 0 Then
						leftstring = Left(line, equalpos - 1 )
						leftstring = Trim(leftstring)
						If LCase(leftstring) = LCase(item) Then
							ReadIni = Mid( line, equalpos + 1 )
							ReadIni = Trim(ReadIni)
							Exit Do
						End If
					End If

					If ini.AtEndOfStream Then Exit Do
					line = ini.ReadLine
					line = Trim(line)
				Loop
				Exit Do
			End If
		Loop
		ini.Close

	End Function

	Public Sub WriteIni( file, section, item, myvalue )

		Dim in_section, section_exists, item_exists, wrote, itemtrimmed
		Dim read_ini, write_ini, temp_ini, linetrimmed, line, equalpos
		Dim leftstring

		in_section = False
		section_exists = False
		item_exists = ( ReadIni( file, section, item ) <> "" )
		wrote = False
		file = Trim(file)
		itemtrimmed = Trim(item)
		myvalue = Trim(myvalue)

		temp_ini = oFSO.GetParentFolderName(file) & "\" & oFSO.GetTempName

		Set read_ini = oFSO.OpenTextFile( file, 1, True, False )
		Set write_ini = oFSO.CreateTextFile( temp_ini, False)

		While read_ini.AtEndOfStream = False
			line = read_ini.ReadLine
			linetrimmed = Trim(line)
			If wrote = False Then
				If LCase(line) = "[" & LCase(section) & "]" Then
					section_exists = True
					in_section = True
				ElseIf InStr( line, "[" ) = 1 Then
					in_section = False
				End If
			End If

			If in_section Then
				If itemtrimmed = "" then
					' Do nothing: we want to wipe the section
				ElseIf item_exists = False Then
					write_ini.WriteLine line
					If myvalue <> "" then
						write_ini.WriteLine item & "=" & myvalue
					End if
					wrote = True
					in_section = False
				Else
					equalpos = InStr(1, line, "=", 1 )
					If equalpos > 0 Then
						leftstring = Left(line, equalpos - 1 )
						leftstring = Trim(leftstring)
						If LCase(leftstring) = LCase(item) Then
							If myvalue <> "" then
								write_ini.WriteLine itemtrimmed & "=" & myvalue
							End if
							wrote = True
							in_section = False
						End If
					End If
					If Not wrote Then
						write_ini.WriteLine line
					End If
				End If
			Else
				write_ini.WriteLine line
			End If
		Wend

		If section_exists = False and itemtrimmed <> "" Then ' section doesn't exist
			write_ini.WriteLine
			write_ini.WriteLine "[" & section & "]"
			If myvalue <> "" then
				write_ini.WriteLine itemtrimmed & "=" & myvalue
			End if
		End If

		read_ini.Close
		write_ini.Close
		If oFSO.FileExists(file) then
			oFSO.DeleteFile file, True
		End if
		oFSO.CopyFile temp_ini, file, true
		oFSO.DeleteFile temp_ini, True

	End Sub

	Public Function Sections(file)
		Dim oContents
		Dim line, equalpos, leftstring, ini

		Set oContents = CreateObject("Scripting.Dictionary")
		file = Trim(file)

		On Error Resume Next
		Set ini = oFSO.OpenTextFile( file, 1, False)
		If Err then
			Err.Clear
			Exit Function
		End if
		On Error Goto 0

		Do While ini.AtEndOfStream = False
			line = ini.ReadLine
			line = Trim(line)
			If Left(line,1) = "[" then
				equalpos = Instr(line, "]")
				leftstring = Mid(line, 2, equalpos - 2)
				oContents.Add leftstring, ""
			End if
		Loop

		Set Sections = oContents

	End Function

	Public Function SectionContents(file, section)
		Dim oContents
		Dim line, equalpos, leftstring, ini

		Set oContents = CreateObject("Scripting.Dictionary")
		file = Trim(file)
		section = Trim(section)

		On Error Resume Next
		Set ini = oFSO.OpenTextFile( file, 1, False)
		If Err then
			Err.Clear
			Exit Function
		End if
		On Error Goto 0

		Do While ini.AtEndOfStream = False
			line = ini.ReadLine
			line = Trim(line)
			If LCase(line) = "[" & LCase(section) & "]" Then
				line = ini.ReadLine
				line = Trim(line)
				Do While Left( line, 1) <> "["
					'If InStr( 1, line, item & "=", 1) = 1 Then
					equalpos = InStr(1, line, "=", 1 )
					If equalpos > 0 Then
						leftstring = Left(line, equalpos - 1 )
						leftstring = Trim(leftstring)
					    oContents(leftstring) = Trim(Mid(line, equalpos + 1 ))
					End If

					If ini.AtEndOfStream Then Exit Do
    				line = ini.ReadLine
	    			line = Trim(line)
		        Loop
                Exit Do
			End If
		Loop
		ini.Close
		Set SectionContents = oContents
		
	End Function
    

	Function RunWithHeartbeat(sCmd)

		Dim oExec
		Dim lastHeartbeat
		Dim lastStart
		Dim iHeartbeat
		Dim iMinutes

		' Initialize the last heartbeat time (start the timer) and interval

		lastHeartbeat = Now
		iHeartbeat = 5

		' Start the command

		oLogging.CreateEntry "About to run command: " & sCmd, LogTypeInfo
		lastStart = Now
		Set oExec = oShell.Exec(sCmd)
		Do While oExec.Status = 0

			' Sleep

			WScript.Sleep 500


			' See if it is time for a heartbeat

			If iHeartbeat > 0 and DateDiff("n", lastHeartbeat, Now) > iHeartbeat then
				iMinutes = DateDiff("n", lastStart, Now)
				oLogging.CreateEvent 41003, LogTypeInfo, "ZTI Heartbeat: command has been running for " & CStr(iMinutes) & " minutes (process ID " & oExec.ProcessID & ")", Array(iMinutes)
				lastHeartbeat = Now
			End if

		Loop


		' Return the exit code to the caller

		oLogging.CreateEntry "Return code from command = " & oExec.ExitCode, LogTypeInfo
		RunWithHeartbeat = oExec.ExitCode


	End Function


	Function ComputerName

		Dim re
		Dim sComputer


		Set re = New RegExp


		' Figure out the computer name to include

		If oEnvironment.Item("_SMSTSMachineName") <> "" then
			sComputer = oEnvironment.Item("_SMSTSMachineName")
		ElseIf oEnvironment.Item("OSDCOMPUTERNAME") <> "" and Instr(oEnvironment.Item("OSDCOMPUTERNAME"),":") = 0 then
			sComputer = oEnvironment.Item("OSDCOMPUTERNAME")
		ElseIf oEnvironment.Item("OSDNEWMACHINENAME") <> "" then
			sComputer = oEnvironment.Item("OSDNEWMACHINENAME")
		ElseIf oEnvironment.Item("ComputerName") <> "" then
			sComputer = oEnvironment.Item("ComputerName")
		ElseIf oEnvironment.Item("OSVersion") = "WinPE" and oEnvironment.Item("OSDCOMPUTERNAME") <> "" then
			sComputer = oEnvironment.Item("OSDCOMPUTERNAME")
			re.Pattern = ":"
			sComputer = re.Replace(sComputer, "")
		Else
			sComputer = oEnvironment.Item("HostName")
		End if

		ComputerName = oEnvironment.Substitute(sComputer)

	End Function


	Function FindFile(sFilename, sFoundPath)

		Dim iRetVal 
		Dim sDir


		iRetVal = Failure
		sFoundPath = ""


		' Look through the standard locations

		If oEnvironment.Item("DeployRoot") <> "" then

			For each sDir in Array("\", "\Servicing\", "\Tools\", "\USMT\", "\Templates\", "\Scripts\", "\Control\")

				If oFSO.FileExists(oEnvironment.Item("DeployRoot") & sDir & sFileName) then
					sFoundPath = oEnvironment.Item("DeployRoot") & sDir & sFileName
					iRetVal = Success
					Exit For
				ElseIf oEnvironment.Item("Architecture") <> "" and oFSO.FileExists(oEnvironment.Item("DeployRoot") & sDir & oEnvironment.Item("Architecture") & "\" & sFileName) then
					sFoundPath = oEnvironment.Item("DeployRoot") & sDir & oEnvironment.Item("Architecture") & "\" & sFileName
					iRetVal = Success
					Exit For
					
			    ' oEnvironment.Item("Architecture") *can* be blank, search in the %Processor_Architecture% directory.
				ElseIf ucase(oEnv("Processor_Architecture")) = "AMD64" and oFSO.FileExists(oEnvironment.Item("DeployRoot") & sDir & "x64\" & sFileName) then
					sFoundPath = oEnvironment.Item("DeployRoot") & sDir & "x64\" & sFileName
					iRetVal = Success
					Exit For
				ElseIf oFSO.FileExists(oEnvironment.Item("DeployRoot") & sDir & oEnv("Processor_Architecture") & "\" & sFileName) then
					sFoundPath = oEnvironment.Item("DeployRoot") & sDir & oEnv("Processor_Architecture") & "\" & sFileName
					iRetVal = Success
					Exit For
				End if

			Next

		End if
		If sFoundPath <> "" then
			FindFile = iRetVal
			Exit Function
		End if


		' Check resource root locations (normally OSD only)

		If oEnvironment.Item("ResourceRoot") <> "" and oEnvironment.Item("ResourceRoot") <> oEnvironment.Item("DeployRoot") then

			For each sDir in Array("\", "\Servicing\", "\Tools\", "\USMT\", "\Templates\", "\Scripts\", "\Control\")

				If oFSO.FileExists(oEnvironment.Item("ResourceRoot") & sDir & sFileName) then
					sFoundPath = oEnvironment.Item("ResourceRoot") & sDir & sFileName
					iRetVal = Success
					Exit For
				ElseIf oFSO.FileExists(oEnvironment.Item("ResourceRoot") & sDir & oEnvironment.Item("Architecture") & "\" & sFileName) then
					sFoundPath = oEnvironment.Item("ResourceRoot") & sDir & oEnvironment.Item("Architecture") & "\" & sFileName
					iRetVal = Success
					Exit For
				End if

			Next

		End if
		If sFoundPath <> "" then
			FindFile = iRetVal
			Exit Function
		End if


		' Now look in the log directory, this directory, working directory, and SYSTEM32 directory

		If oFSO.FileExists(oUtility.LogPath & "\" & sFilename) then
			sFoundPath = oUtility.LogPath & "\" & sFilename
			iRetVal = Success
		ElseIf oFSO.FileExists(oUtility.ScriptDir & "\" & sFilename) then
			sFoundPath = oUtility.ScriptDir & "\" & sFilename
			iRetVal = Success
		ElseIf oFSO.FileExists(oUtility.LocalRootPath & "\" & sFileName) then
			sFoundPath = oUtility.LocalRootPath & "\" & sFileName
			iRetVal = Success
		ElseIf oFSO.FileExists(".\" & sFileName) then
			sFoundPath = oShell.CurrentDirectory & "\" & sFilename
			iRetVal = Success
		ElseIf oFSO.FileExists(oEnvironment.Substitute("%WINDIR%\SYSTEM32\" & sFilename)) then
			sFoundPath = oEnvironment.Substitute("%WINDIR%\SYSTEM32\" & sFilename)
			iRetVal = Success
		ElseIf oEnvironment.Item("OSDPACKAGEPATH") <> "" and oFSO.FileExists(oEnvironment.Item("OSDPACKAGEPATH") & "\" & sFilename) then
			sFoundPath = oEnvironment.Item("OSDPACKAGEPATH") & "\" & sFilename
			iRetVal = Success
		ElseIf oEnvironment.Item("_SMSTSPackagePath") <> "" and oFSO.FileExists(oEnvironment.Item("_SMSTSPackagePath") & "\" & sFilename) then
			sFoundPath = oEnvironment.Item("_SMSTSPackagePath") & "\" & sFilename
			iRetVal = Success
		End if

		If iRetVal <> Success then
			oLogging.CreateEntry "FindFile: The file " & sFilename & " could not be found in any standard locations.", LogTypeInfo
		End if
	
		FindFile = iRetVal

	End Function


	Function FindMappedDrive(sServerUNC)

		Dim arrSplit
		Dim sServerShare, sServerName
		Dim arrDrives, i


		' If the UNC isn't a UNC, just return the drive letter

		If Len(sServerUNC) < 3 then
			FindMappedDrive = ""
			EXIT FUNCTION
		End if
		If Mid(sServerUNC,2,2) = ":\" then
			FindMappedDrive = Left(sServerUNC, 2)
			EXIT FUNCTION
		End if


		' Build the UNC

		If Instr(Mid(sServerUNC, 3), "\") <= 0 or Left(sServerUNC, 2) <> "\\" then
			FindMappedDrive = ""
			EXIT FUNCTION
		End if
		arrSplit = Split(Mid(sServerUNC,3), "\")		
		sServerName = arrSplit(0)
		sServerShare = "\\" & sServerName & "\" & arrSplit(1)


		' Look to see if this is a mapped drive
 
		On Error Resume Next
		Set arrDrives = oNetwork.EnumNetworkDrives
		If Err then
			FindMappedDrive = ""
			Exit Function
		Else
			On Error Goto 0
			For i = 0 to arrDrives.Count - 1 Step 2
				If UCase(sServerShare) = UCase(arrDrives.Item(i+1)) and arrDrives.Item(i) <> "" then
					FindMappedDrive = arrDrives.Item(i)
					Exit Function
				End if
			Next
		End if
		On Error Goto 0

		FindMappedDrive = ""

	End Function


	Function ValidateConnection(sServerUNC)

		Dim iRetVal
		Dim arrSplit
		Dim sServerShare, sServerName, sFoundDrive
		Dim sOSDConnectToUNC, sRIPInfo, sCmd
		Dim sWizardHTA, sUserID
		Dim sCurrentServerName

		' Make sure a UNC is specified

		If sServerUNC = "" then
			oLogging.CreateEntry "WARNING - Unable to validation connection because a blank UNC was specified.", LogTypeWarning
			ValidateConnection = Failure
			EXIT FUNCTION
		End if
		If Mid(sServerUNC,2,2) = ":\" then
			oLogging.CreateEntry "Using a local or mapped drive, no connection is required.", LogTypeInfo
			ValidateConnection = Success
			EXIT FUNCTION
		End if

		oLogging.CreateEntry "Validating connection to " & sServerUNC, LogTypeInfo


		' See if we've already connected

		arrSplit = Split(Mid(sServerUNC,3), "\")		
		sServerName = arrSplit(0)
		sServerShare = "\\" & sServerName & "\" & arrSplit(1)
		
		' This isn't necessary if we're trying to connect to the current DP, so check that.

		If Left(oUtility.ScriptDir, 2) = "\\" then

			arrSplit = Split(Mid(oUtility.ScriptDir, 3), "\")
			sCurrentServerName = arrSplit(0)
			If UCase(sServerName) = UCase(sCurrentServerName) then
				oLogging.CreateEntry "Already connected to server " & sServerName & " as that is where this script is running from.", LogTypeInfo
				ValidateConnection = Success
				EXIT FUNCTION
			End if

		End if



		' Now see if there is already a server connection.

		If dicNetworkConnections.Exists(sServerName) then
			oLogging.CreateEntry "Already connected to server " & sServerName, LogTypeInfo
			ValidateConnection = Success
			EXIT FUNCTION
		End if


		'Map the SCCM user variables to the CS.INI variables

                If oEnvironment.Item("_SMSTSReserved1") <> "" AND oEnvironment.Item("_SMSTSReserved2") <> "" Then
			OEnvironment.Item("UserDomain") = Left(oEnvironment.Item("_SMSTSReserved1"),Instr(oEnvironment.Item("_SMSTSReserved1"),"\")-1)
                        oEnvironment.Item("UserID")= Mid(oEnvironment.Item("_SMSTSReserved1"),Instr(oEnvironment.Item("_SMSTSReserved1"),"\")+1)
			oEnvironment.Item("UserPassword")=oEnvironment.Item("_SMSTSReserved2")
		End if

		
		'  It is possible that the server allows anonymous connections, skip if the share is readable.
		'if oFso.FolderExists(sServerName) then
		'	oLogging.CreateEntry "Already connected to server " & sServerName, LogTypeInfo
		'	ValidateConnection = Success
		'	EXIT FUNCTION
		'end if


		' Try to find OSDConnectToUNC.exe

		iRetVal = FindFile("OSDConnectToUNC.exe", sOSDConnectToUNC)
		If iRetVal = Success then

			arrSplit = Array("","","")


			' See if OSD variable with connection account is available

			If oEnvironment.Item("_OSDRESERVED1") <> "" then
				arrSplit = split(oEnvironment.Item("_OSDRESERVED1"),",")
			End if


			' Still no connection info?  Look for RIPINFO

			If arrSplit(0) = "" then
				oLogging.CreateEntry "Attempting to find RIPINFO.INI for connection information.", LogTypeInfo
				iRetVal = FindFile("RIPINFO.INI", sRIPInfo)
				If iRetVal = Success then
					arrSplit(0) = oUtility.ReadIni(sRIPInfo, "RIPINFO", "Reserved1")
					arrSplit(1) = oUtility.ReadIni(sRIPInfo, "RIPINFO", "Reserved2")
					arrSplit(2) = oUtility.ReadIni(sRIPInfo, "RIPINFO", "Reserved3")
					oLogging.CreateEntry "RIPINFO file located and read.", LogTypeInfo
				Else
					oLogging.CreateEntry "WARNING - Unable to find RIPINFO.INI.", LogTypeWarning
				End if
			End if


			' If we have connection info, call OSDConnectToUNC.  Otherwise, log the failure.

			If arrSplit(0) <> "" then

				oLogging.CreateEntry "About to call OSDConnectToUNC to connect to " & sServerShare, LogTypeInfo
				sCmd = "cmd /c """"" & sOSDConnectToUNC & """ """ & UCase(sServerShare) & """ " & arrSplit(0) & " " & arrSplit(1) & " " & arrSplit(2) & """"

				On Error Resume Next

				iRetVal = oShell.Run(sCmd, 0, true)
				If Err then
					oLogging.CreateEntry "WARNING - Error executing OSDConnectToUNC,  " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
				ElseIf iRetVal = 0 then
					oLogging.CreateEntry "Successfully established connection using OSD.", LogTypeInfo
					dicNetworkConnections.Add sServerName, sServerShare
				Else
					oLogging.CreateEntry "WARNING - Unable to establish connection using OSD, OSDConnectToUNC rc = " & iRetVal, LogTypeWarning
				End if

				On Error Goto 0

			Else
				oLogging.CreateEntry "WARNING - Unable to find OSD connection information, OSDConnectToUNC not possible.", LogTypeWarning
				iRetVal = Failure
			End if
			
		Else

			' If no credentials are available, prompt.

			If oEnvironment.Item("UserID") = "" or oEnvironment.Item("UserPassword") = "" or (  oEnvironment.Item("UserDomain") = "" and _
			   instr(1,oEnvironment.Item("UserID"),"\",vbTextCompare) = 0 and instr(1,oEnvironment.Item("UserID"),"@",vbTextCompare) = 0 ) then

				' Find the HTA that prompts for credentials

				iRetVal = FindFile("Wizard.hta", sWizardHTA)
				If iRetVal <> Success then
					oLogging.CreateEntry "ERROR - Unable to find Wizard.hta, so it is impossible to prompt for credentials.", LogTypeError
					ValidateConnection = Failure
					Exit Function
				End if


				' Execute the HTA

				oShell.Run "mshta.exe """ & sWizardHTA & """ /NotWizard /LeaveShareOpen /ValidateAgainstUNCPath:""" & sServerShare & """ Credentials_ENU.xml", 1, true

				' See if the values are populated now

				If oEnvironment.Item("UserID") = "" then
					oLogging.CreateEntry "ERROR - no credentials were returned from LTICredentials.hta, so no connection is possible.", LogTypeError
					ValidateConnection = Failure
					Exit Function
				End if

			End if


			' Map a drive

			If oEnvironment.Item("UserDomain") <> "" then
				sUserID = oEnvironment.Item("UserDomain") & "\" & oEnvironment.Item("UserID")
			Else
				sUserID = oEnvironment.Item("UserID")
			End if

			iRetVal = MapNetworkDrive(sServerShare, sUserID, oEnvironment.Item("UserPassword"))
			If iRetVal <> Success then
				oLogging.CreateEntry "ERROR - Unable to map a network drive to " & sServerShare & ".", LogTypeError
				ValidateConnection = Failure
				Exit Function
			End if


			' Record the mapped drive

			oLogging.CreateEntry "Successfully established connection using supplied credentials.", LogTypeInfo
			dicNetworkConnections.Add sServerName, sServerShare

			iRetVal = Success

		End if


		ValidateConnection = iRetVal

	End Function

	
	' For Backwards Compatiblity

	Function MapNetworkDrive (sShare, sDomID, sDomPwd )
		If Len(MapNetworkDriveEx (sShare, sDomID, sDomPwd )) = 2 then
			MapNetworkDrive = success
		Else
			MapNetworkDrive = failure
		End if
	End function


	'
	' maps a drive letter to the sShare UNC path. 
	'   Returns the drive letter example: "C:", otherwise returns an error string!
	'   sDomID and sDomPwd can be EMPTY.
	'
	Function MapNetworkDriveEx (sShare, sDomID, sDomPwd )
    
		Dim sDrive
		Dim HasError
		Dim ErrDesc
		Dim i
		Dim arrDrives


		' Make sure networking is initialized

		On Error Resume Next
		Set arrDrives = oNetwork.EnumNetworkDrives
		If Err then
			oLogging.CreateEntry "Unable to enumerate network drives (is the network initialized?): " & Err.Description & " (" & Err.Number & ")", LogTypeWarning
		Else
	
			' Find any previous connections

			For i = 0 to arrDrives.Count - 1 Step 2
				If UCase(sShare) = UCase(oNetwork.EnumNetworkDrives.Item(i+1)) then
					MapNetworkDriveEx = oNetwork.EnumNetworkDrives.Item(i)
					oLogging.CreateEntry "Found Existing UNC Path " & MapNetworkDriveEx & "  = " & sShare , LogTypeInfo
					Exit function
				End if
			Next

		End if
		On Error Goto 0

       
		' Find the first avaiable drive letter

		For sDrive = asc("Z") to asc("C") step -1

			On Error Resume Next
			oNetwork.MapNetworkDrive  chr(sDrive)&":", sShare, False, sDomID, sDomPwd
			HasError = err.number
			ErrDesc = err.Description
			On Error Goto 0
          
			Select case HasError
			Case 0          ' No Error, SUCCESS
				MapNetworkDriveEx = chr(sDrive)&":"
				oLogging.CreateEntry "Mapped Network UNC Path " & MapNetworkDriveEx & "  = " & sShare , LogTypeInfo
				Exit function 
			Case &h80070055 ' The local device name is already in use.
			Case &h800704B2 ' The local device name has a remembered connection to another network resource.
			Case Else
			' Case &h800704C3 ' Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed.
			' Case &h8007052E ' Logon failure: unknown user name or bad password.
             
			' There was a some kind of fatal error.     
			If ErrDesc <> "" then
				MapNetworkDriveEx = ErrDesc
			Else
				MapNetworkDriveEx = "Unable to map UNC Path " & sShare & " :" & "( 0x" & hex(HasError) & " ) " 
			End if
			oLogging.CreateEntry MapNetworkDriveEx & "", LogTypeError                 
			Exit function 

			End select

		Next
    
		MapNetworkDriveEx = "Unable to map UNC Path " & sShare & " : No avaiable local device names! " 
		oLogging.CreateEntry MapNetworkDriveEx , LogTypeError
       
	End function


	Public Function VerifyPathExists(strPath)

		If strPath = "" then
			VerifyPathExists = True
			Exit Function
		End if
		If oFSO.FolderExists(strPath) then
			VerifyPathExists = true
			Exit Function
		Else
			VerifyPathExists oFSO.GetParentFolderName(strPath)
			On Error Resume Next
			oFSO.CreateFolder strPath
			On Error Goto 0
		End if

	End Function
	

	'
	' Create an XMLDOM Object
	'
	Function CreateXMLDOMObjectEx( FileName )

		Set CreateXMLDOMObjectEx = nothing
		On Error Resume Next
		Set CreateXMLDOMObjectEx = CreateObject("MSXML2.DOMDocument")
                    
		If CreateXMLDOMObjectEx is nothing then
			Exit function 
		End if
        
		CreateXMLDOMObjectEx.Async = FALSE
        
		If IsEmpty(FileName) then
			Exit function 
		End if
        
        	If not CreateXMLDOMObjectEx.Load (FileName ) then
                
			oLogging.CreateEntry "XMLDOM: File: " & FileName & " Line: " & _
			CreateXMLDOMObjectEx.ParseError.Line & vbNewLine & _
			CreateXMLDOMObjectEx.ParseError.Reason & vbNewLine & _ 
			CreateXMLDOMObjectEx.ParseError.SrcText , LogTypeInfo                  
			Set CreateXMLDOMObjectEx = nothing
            
		End if
        
	End function    	


	Function CreateXMLDOMObject 
		Set CreateXMLDOMObject = CreateXMLDOMObjectEx( empty )
	End function 


	Function BDDUtility

		Dim sBDDUtility
		Dim iRetval
		Dim sCmd


		' Already retrieved an instance?  Return it.

		If not (oBDDUtility is Nothing) then
			Set BDDUtility = oBDDUtility
			Exit Function
		End if
		
		' Allready Registered
				
		on error resume next
		    Set oBDDUtility = CreateObject("Microsoft.BDD.Utility")
		    If not (oBDDUtility is Nothing) then
			    Set BDDUtility = oBDDUtility
			    Exit Function
		    End if
		on error goto 0


		' Find the Microsoft.BDD.Utility.dll file

		iRetVal = FindFile("Microsoft.BDD.Utility.dll", sBDDUtility)
		If iRetVal <> Success then
			oLogging.CreateEntry "ERROR: Unable to find Microsoft.BDD.Utility.dll", LogTypeError
			Set BDDUtility = Nothing
			Exit Function
		End if


		' Register the DLL

		sCmd = "regsvr32.exe /s """ & sBDDUtility & """"
		iRetVal = oShell.Run(sCmd, 0, true)
		If iRetVal <> 0 then
			oLogging.CreateEntry "ERROR: Unable to register Microsoft.BDD.Utility.dll", LogTypeError
			Set BDDUtility = Nothing
			Exit Function
		End if


		' Create an instance

		Set oBDDUtility = CreateObject("Microsoft.BDD.Utility")
		Set BDDUtility = oBDDUtility	
		
	End Function


	Sub SetTaskSequenceProperties(tsID)

		Dim oTaskSequences 
		Dim oTaskSequence
		Dim oOperatingSystems
		Dim oOS
		Dim oImageLang
		Dim sImageLang
		Dim oLanguage
		Dim sImagePath
		Dim oWDSServer
		Dim sWDSServer
		Dim objTmp
		Dim oTS
		Dim oOSGUID


		' If there is task sequence ID set, get the properties

		If tsID <> "" then

			tsID = UCase(tsID)


			' Get the build record

			Set oTaskSequences = oUtility.CreateXMLDOMObjectEx(oEnvironment.Item("DeployRoot") & "\Control\TaskSequences.xml")
			Set oTaskSequence = oTaskSequences.selectSingleNode("//ts[ID='" & tsID & "']")
			If oTaskSequence is Nothing then
				oLogging.CreateEntry "ERROR: Invalid task sequence ID " & tsID & " specified", LogTypeError
				Exit Sub
			End if


			' Set the simple build properties

			oEnvironment.Item("TaskSequenceName") = oTaskSequence.SelectSingleNode("Name").text
			oEnvironment.Item("TaskSequenceVersion") = oTaskSequence.SelectSingleNode("Version").text


			' Load the TS.XML and get the OSGUID

			Set oTS = oUtility.CreateXMLDOMObjectEx(oEnvironment.Item("DeployRoot") & "\Control\" & tsID & "\TS.xml")
			Set oOSGUID = oTS.SelectSingleNode("//globalVarList/variable[@name='OSGUID']")		
			If oOSGUID is Nothing then
				
				Exit Sub
			End if
			oEnvironment.Item("OSGUID")=oOSGUID.text


			' Get the OS record

			Set oOperatingSystems = oUtility.CreateXMLDOMObjectEx(oEnvironment.Item("DeployRoot") & "\Control\OperatingSystems.xml")
			Set oOS = oOperatingSystems.selectSingleNode("//os[@guid='" & oOSGUID.text & "']")
			If oOS is Nothing then
				Alert oOperatingSystems.xml
				oLogging.CreateEntry "ERROR: Invalid OS GUID " & oOSGUID.text & " specified for task sequence " & tsID & " specified", LogTypeError
				Exit Sub
			End if


			' Set the simple OS properties

			oEnvironment.Item("ImageIndex") = oOS.selectSingleNode("ImageIndex").text
			oEnvironment.Item("ImageSize") = oOS.selectSingleNode("Size").text
			oEnvironment.Item("ImageFlags") = oOS.selectSingleNode("Flags").text
			oEnvironment.Item("ImageBuild") = oOS.selectSingleNode("Build").text
			oEnvironment.Item("ImageProcessor") = oOS.selectSingleNode("Platform").text


			' Get the languages

			Set oImageLang = oOS.selectNodes("Language")
		        sImageLang = ""
			If not (oImageLang is Nothing) then
				For each oLanguage in oImageLang			    
					sImageLang = sImageLang & oLanguage.text & vbTab			       
				Next
			End if
			If right(sImageLang,1) = vbTab then
				sImageLang = Left(sImageLang, Len(sImageLang)-1)  ' Remove trailing tab
			End if
			oEnvironment.ListItem("ImageLanguage") = split(sImageLang, vbTab)


			' Set the image path

			sImagePath = oOS.selectSingleNode("ImageFile").text
			If sImagePath = "" and left(oEnvironment.Item("ImageBuild"),1) = "5" then
			    sImagePath = "."
			End if
			
			If Left(sImagePath, 1) = "." then

				' See if this is a WDS image

				Set oWDSServer = oOS.selectSingleNode("WDSServer")
				If not (oWDSServer is Nothing) then
					sWDSServer = oWDSServer.Text
				End if


				' Make sure that's where we want to pull it from

				If sWDSServer <> "" then
					If oEnvironment.Item("WDSServer") <> "" then
						sWDSServer = oEnvironment.Item("WDSServer")
					End if
				End if


				' Set the actual image path
	
				If sWDSServer <> "" then
					sImagePath = "\\" & sWDSServer & "\REMINST" & Mid(sImagePath, 2)
				Else
					sImagePath = oEnvironment.Item("DeployRoot") & Mid(sImagePath, 2)
				End if

			End if

            oLogging.CreateEntry "InstallFromPath: " & sImagePath, LogTypeInfo
            oEnvironment.Item("InstallFromPath") = NormalizePath(sImagePath)

			' Set validation requirements based on ImageBuild

			If Left(oEnvironment.Item("ImageBuild"), 1) = "5" then

				oEnvironment.Item("ImageMemory") = "128"   ' For XP, it will run in 128MB - but only if we're not running a RAMdisk PE image...
				oEnvironment.Item("ImageProcessorSpeed") = "400"

			Else

				oEnvironment.Item("ImageMemory") = "512"   ' For Vista, 512MB is needed
				oEnvironment.Item("ImageProcessorSpeed") = "800"

			End if

		End if

	End Sub


	Function NormalizePath( Path )
		Dim i, j
		Dim SplitPath
       
		SplitPath = Split(Replace(Path, """", "" ), "\" )
       
		For i = 1 to UBound(SplitPath) - 1
			If SplitPath(i) = "." then
				SplitPath(i) = empty
			Elseif SplitPath(i) = ".." then
				SplitPath(i) = empty
				For j = i-1 to 0  step -1
					If SplitPath(j) <> empty then
						SplitPath(j) = empty 
						Exit for
					End if 
				Next
			End if  
		Next  
          
		Normalizepath = Join(SplitPath, "\")              
       
		Do while Instr(2, NormalizePath, "\\", vbTextCompare) <> 0
			If left(NormalizePath,2) = "\\" then
				NormalizePath = "\" & Replace(NormalizePath, "\\", "\", 2, -1)       
			Else
				NormalizePath = Replace(NormalizePath, "\\", "\", 1, -1)
			End if 
		Loop
       
	End function 	


	Function IsSupportedPlatform(sPlatform)

		Dim iRetVal
		Dim sSupportedPlatforms
		Dim oPlatformNode
		Dim oNode
		Dim oResults
		Dim oResult
		Dim bFound


		' Special case: check for Windows PE

        If oEnvironment.Item("OSVersion") = "WinPE" then

                If sPlatform = "Windows PE" then
                                IsSupportedPlatform = true
                Else
                                IsSupportedPlatform = false
                End if
                
            Exit Function
        End if


		' Load the XML file if not yet loaded


		If oSupportedPlatforms is Nothing then

			iRetVal = oUtility.FindFile("ZTISupportedPlatforms.xml", sSupportedPlatforms)
			Set oSupportedPlatforms = CreateXMLDOMObjectEx(sSupportedPlatforms)

		End if


		' Find the selected platform

		Set oPlatformNode = oSupportedPlatforms.SelectSingleNode("//SupportedPlatform[@name='" & sPlatform & "']")
		If oPlatformNode is Nothing then
			IsSupportedPlatform = False
			Exit Function
		End if


		' Check each of the expressions.  If any don't return a record, return false

		For each oNode in oPlatformNode.SelectNodes("Expression")

			bFound = false
			Set oResults = objWMI.ExecQuery(oNode.Text)
			For each oResult in oResults
				bFound = true
				Exit For
			Next

			If not bFound then
				oLogging.CreateEntry "Condition " & oNode.Text & " not satisfied, platform " & sPlatform & " is not supported.", LogTypeInfo
				IsSupportedPlatform = False
				Exit Function
			End if

		Next


		' All conditions satisfied, return true

		oLogging.CreateEntry "Platform " & sPlatform & " is supported on this computer.", LogTypeInfo
		IsSupportedPlatform = True		

	End Function

	
	'
	' Given a Group XML FileName, and a list of approved group names. Will return
	' a white dictionary list of approved GUIDs. If empty, everything is approved.
	'
	Function GetWhiteListOfGUIDsFromGroup ( sXMLGroupFile, sGroupName )
	
		Dim iRetVal
		Dim sFoundFile
		Dim oGroups
		Dim oItem
		Dim oEntry
		Dim oName
		Dim oGroupNames
		
		oLogging.CreateEntry "Get Whitelist of GUID's from Group: " & sGroupName & "  File: " &  sXMLGroupFile, LogTypeInfo
		
		set GetWhiteListOfGUIDsFromGroup = CreateObject("Scripting.Dictionary")
		GetWhiteListOfGUIDsFromGroup.CompareMode = vbTextCompare

		If oEnvironment.ListItem(sGroupName).Count = 0 then
			oLogging.CreateEntry "No groups defined, will not apply: " & sXMLGroupFile & ". Use ALL PACKAGES instead.", LogTypeInfo
			Exit function
		End if
		
		set oGroupNames = CreateObject("Scripting.Dictionary")
		oGroupNames.CompareMode = vbTextCompare
		
		for each oItem in oEnvironment.ListItem( sGroupName )
			if not oGroupNames.exists(oItem) then
				oGroupNames.add oItem, ""
			end if
		next 

		iRetVal = oUtility.FindFile(sXMLGroupFile, sFoundFile)
		If iRetVal <> Success then
			oLogging.CreateEntry "Unable to find the " & sXMLGroupFile & " file, assuming no groups for this data set.", LogTypeInfo
			Exit function
		End if
		
		oLogging.CreateEntry "Loading Group File: " & sFoundFile, LogTypeInfo
		Set oGroups = oUtility.CreateXMLDOMObjectEx(sFoundFile)
		If oGroups is nothing then
			oLogging.CreateEntry "Unable to load file " & sFoundFile, LogTypeError
			Exit function 
		End if
		
		oLogging.CreateEntry "Loading Group Whitelist...", LogTypeInfo
		
		' Enumerate thorough all group items
		for each oItem in oGroups.selectNodes("*/*[@guid and Name]")
		
			set oName = oItem.selectSingleNode("Name")
			If not oName is nothing then
				If oGroupNames.exists(oName.text) then
					oLogging.CreateEntry vbTab & "Approved Package: " & oName.text, LogTypeInfo
					for each oEntry in oItem.selectNodes("*")
						If oEntry.nodeName <> "Name" then
							If not GetWhiteListOfGUIDsFromGroup.exists(oEntry.text) then
								oLogging.CreateEntry vbTab & vbTab & " Approved GUID: " & oEntry.text, LogTypeInfo
								GetWhiteListOfGUIDsFromGroup.Add oEntry.text, oName.text
							Else
								oLogging.CreateEntry vbTab & vbTab & "Duplicate GUID: " & oEntry.text, LogTypeInfo
							End if
						End if
					next
				End if 
				
			End if
		
		next
		oLogging.CreateEntry "Finished with Group Whitelist. Count = " & GetWhiteListOfGUIDsFromGroup.Count, LogTypeInfo

		If GetWhiteListOfGUIDsFromGroup.Count = 0 then
			' Just to be sure we always have the filter on (sucks but those are the rules).
			GetWhiteListOfGUIDsFromGroup.Add "{" & left( CreateObject("Scriptlet.TypeLib").GUID, 38 ) & "}", "Random GUID"
		End if
		
		
	End Function


	Function SafeAsc ( sPlainText, pos ) 
		if VarType(sPlainText) = (vbArray or vbByte) then
			SafeAsc =  cint(midb(sPlainText, pos + 1 , 1))
		elseif (pos + 1) <= len(sPlainText) then
			SafeAsc = asc(mid(sPlainText, pos + 1, 1))
		else
			SafeAsc = 0
		end if 
	End Function

	Function SafeEnc( n, x ) 
		Const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
		SafeEnc = mid(BASE64_TABLE, ((n\(2^x)) and 63) + 1,1)
	End Function 

	Function base64Encode( sPlainText )
		Dim i, n

		if 0 < len(sPlainText) then
			for i = 0 to len(sPlainText) - 1 step 3
				' Add a new line ...
				if i > 0  and i mod 57  = 0 then
					base64Encode = base64Encode & vbNewLine  
				end if 
				' three 8-bit characters become one 24-bit number
				n = (SafeAsc(sPlainText,i)*&h10000 + SafeAsc(sPlainText,i+1)*&h100 + SafeAsc(sPlainText,i+2))

				' the 24-bit number becomes four 6-bit numbers
				base64Encode = base64Encode & SafeEnc( n, 18 ) & SafeEnc( n, 12 ) & SafeEnc( n, 6 ) & SafeEnc( n, 0 ) 
			next
		end if
	    
		' Pad Text at End of String
		n = (3-(len(sPlainText)mod 3)) mod 3
		base64Encode = left ( base64Encode, len(base64Encode) - n ) + string( n, "=" )   
	    
	End Function

	Function SafeDecode( s, i, x ) 
		Const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
		SafeDecode = ( InStr(1, BASE64_TABLE, mid(s,i,1), vbBinaryCompare) - 1) * (2 ^ x)
	End Function 

	Function base64Decode( sEncodedText )

	Dim sEncText 
	Dim regex   
	Dim p, i, n
	   
	' Remove all non base64 text 
	set regex = new RegExp


	regex.pattern = "[^=" & BASE64_TABLE & "]"  
	regex.global = true 
	
	sEncText = regex.Replace(sEncodedText,"")
	sEncText = replace( sEncText, vbLF, "")
	sEncText = replace( sEncText, vbCR, "")
	   
	' Verify String is in Base64 format (multiple of 4 chars)
	if len(sEncText) mod 4 <> 0 then
		oLogging.CreateEntry "OSDBitLockerStartupKey is not a valid string (not Base64 Format)", LogTypeError
		exit function 
	end if
	   
	if right(sEncText,2) = "==" then
		p = 2
	elseif right(sEncText,1) = "=" then
		p = 1
	end if
	sEncText = left(sEncText,len(sEncText)-p) & string(p,"A")
	   
	for i = 1 to len(sEncText) step 4
		' Convert four 6-bit numbers into one 24 bit value
		n = SafeDecode(sEncText,i+3,0) + SafeDecode(sEncText,i+2,6) + SafeDecode(sEncText,i+1,12) + SafeDecode(sEncText,i+0,18)
	   
		' Convert the 24-bit value back into three 8-bit values.
		base64Decode = base64Decode & chr( (n \ (2^16)) and 255 ) & chr( (n \ (2^8)) and 255 ) & chr( n and 255 ) 
	       
	next
	   
	' Trim off any excess space.
	base64Decode = left(base64Decode,len(base64Decode)-p)
	End Function
	

End Class


Class Database

	Private sIniFile
	Private sSection
	Private dicSQLData
	Private oConn

	Private Sub Class_Initialize
		
		Dim sFoundIniFile


		' Figure out where the CustomSettings.ini file is
	
		sIniFile = oUtility.Arguments("inifile")

		If Len(sIniFile) = 0 then
			iRetVal = oUtility.FindFile("CustomSettings.ini", sIniFile)
			If iRetVal <> Success then
				oLogging.CreateEntry "Unable to find CustomSettings.ini, rc = " & iRetVal, LogTypeError
				Exit Sub
			End if
			oLogging.CreateEntry "Using DEFAULT VALUE: Ini file = " & sIniFile, LogTypeInfo
		Else
			If not oFSO.FileExists(sIniFile) then
				iRetVal = oUtility.FindFile(sIniFile, sFoundIniFile)
				If iRetVal = Success then
					sIniFile = sFoundIniFile
				End if
			End if
			oLogging.CreateEntry "Using COMMAND LINE ARG: Ini file = " & sIniFile, LogTypeInfo
		End if

		If Not oFSO.FileExists(sIniFile) then
			oLogging.CreateEntry "Specified INI file does not exist (" & sIniFile & ").", LogTypeError
			Exit Sub
		End if 


		' Create a dictionary object to hold the SQL info

		Set dicSQLData = CreateObject("Scripting.Dictionary")
		dicSQLData.CompareMode = TextCompare

		Set oConn = Nothing

	End Sub


	Public Property Let SectionName(sSect)

		Dim iRetVal, re, sElement
		Dim arrSQLDataKeys, sTmpVal
		Dim arrParameters

		iRetVal = Failure
		Set re = new regexp
		re.IgnoreCase = True
		re.Global = True


		sSection = oEnvironment.Substitute(sSect)
		oLogging.CreateEntry "CHECKING the [" & sSection & "] section", LogTypeInfo


		' Get the "normal" values

		dicSQLData("Table") = ""
		dicSQLData("StoredProcedure") = ""
		arrSQLDataKeys = Array("SQLServer", "Instance", "Port", "Database", "Netlib", "Table", "StoredProcedure", "DBID", "DBPwd", "SQLShare", "ParameterCondition")
		for each sElement in arrSQLDataKeys
			sTmpVal = oUtility.ReadIni(sIniFile, sSection, sElement)
			if Len(sTmpVal) = 0 then
				oLogging.CreateEntry sElement & " key not defined in the section [" & sSection & "]", LogTypeInfo
			else
				dicSQLData(sElement) = oEnvironment.Substitute(sTmpVal)
				if Instr(UCase(sElement),"PWD") > 0 then
					oLogging.CreateEntry "Using from [" & sSection & "]: " & sElement & " = ********", LogTypeInfo
				else
					oLogging.CreateEntry "Using from [" & sSection & "]: " & sElement & " = " & sTmpVal, LogTypeInfo
				end if
			end if
		next


		' Handle "Parameters" differently

		sTmpVal = oUtility.ReadIni(sIniFile, sSection, "Parameters")
		If Len(sTmpVal) = 0 then
			oLogging.CreateEntry "No parameters to include in the SQL call were specified", LogTypeInfo
			arrParameters = Array()
		Else
			arrParameters = Split(sTmpVal, ",")
		End if
		dicSQLData("Parameters") = arrParameters


		' Handle "Order" differently

		sTmpVal = oUtility.ReadIni(sIniFile, sSection, "Order")
		If Len(sTmpVal) = 0 then
			arrParameters = Array()
		Else
			arrParameters = Split(sTmpVal, ",")
		End if
		dicSQLData("Order") = arrParameters


		' Make sure required values were specified

		If Len(dicSQLData("SQLServer")) = 0 then
			oLogging.CreateEntry "ERROR - SQLServer NOT defined in the section [" & sSection & "]", LogTypeError
			Exit Property
		End if

		If Len(dicSQLData("SQLShare")) = 0 then
			oLogging.CreateEntry "Warning - SQLShare NOT defined in the section [" & sSection & "], trusted connection may not be possible.", LogTypeWarning
		End if

		If Len(dicSQLData("Database")) = 0 then
			oLogging.CreateEntry "Database not defined in the section [" & sSection & "]. Using default (BDDAdminDB).", LogTypeInfo
			dicSQLData("Database") = "BDDAdminDB"
		End if
		If Len(dicSQLData("Table")) = 0 and Len(dicSQLData("StoredProcedure")) = 0 then
			oLogging.CreateEntry "Warning - Neither Table or StoredProcedure defined in the section [" & sSection & "]. Using default Table = BDDAdminCore", LogTypeWarning
			dicSQLData("Table") = "BDDAdminCore"
		End if
		If Len(dicSQLData("Netlib")) = 0 then
			oLogging.CreateEntry "Default Netlib of DBNMPNTW (named pipes) will be used for connecting to SQL Server.", LogTypeInfo
			dicSQLData("Netlib") = "DBNMPNTW"
		End if
		If Len(dicSQLData("ParameterCondition")) = 0 then
			oLogging.CreateEntry "Default ParameterCondition 'AND' will be used for building queries with multiple parameters.", LogTypeInfo
			dicSQLData("ParameterCondition") = "AND"
		End if
 

		' Was an instance name specified with the SQLServer name?  If so, split them apart

		If Instr(dicSQLData("SQLServer"), "\") > 0 then
			dicSQLData("Instance") = Mid(dicSQLData("SQLServer"), Instr(dicSQLData("SQLServer"), "\") + 1)
			dicSQLData("SQLServer") = Left(dicSQLData("SQLServer"), Instr(dicSQLData("SQLServer"), "\") - 1)
		End if

	End Property

	Public Function Connect

		Dim sDSNRef
		Dim sMsg


		' Create a new ADO connection object

		On Error Resume Next
		Set oConn = CreateObject("ADODB.Connection")
		If Err then
			oLogging.CreateEntry "ERROR - Unable to create ADODB.Connection object, impossible to query SQL Server: " & Err.Description & " (" & Err.Number & ")", LogTypeError
			Set Connect = Nothing
			Exit Function
		End if
		On Error Goto 0


		' If a SQLShare value is specified, try to establish a connection

		If Len(dicSQLData("DBID")) = 0 or Len(dicSQLData("DBPwd")) = 0 then
			If Len(dicSQLData("SQLShare")) > 0 then
				oUtility.ValidateConnection "\\" & dicSQLData("SQLServer") & "\" & dicSQLData("SQLShare")
			Else
				oLogging.CreateEntry "No SQLShare value was specified, not possible to establish a secure connection.", LogTypeInfo
			End if
		End if


		' Build the connect string

		sDSNRef = "Provider=SQLOLEDB;OLE DB Services=0;Data Source=" & dicSQLData("SQLServer")

		If Len(dicSQLData("Instance")) > 0 then
			sDSNRef = sDSNRef & "\" & dicSQLData("Instance")
		End if
		If Len(dicSQLData("Port")) > 0 then
			sDSNRef = sDSNRef & "," & dicSQLData("Port")
		End if
 
		sDSNRef = sDSNRef & ";Initial Catalog=" & dicSQLData("Database") & ";Network Library=" & dicSQLData("Netlib")

		If len(dicSQLData("DBID")) = 0 OR len(dicSQLData("DBPwd")) = 0 then
			oLogging.CreateEntry "OPENING TRUSTED SQL CONNECTION to server " & dicSQLData("SQLServer") & ".", LogTypeInfo
			sDSNRef = sDSNRef & ";Integrated Security=SSPI"
		Else
			oLogging.CreateEntry "OPENING STANDARD SECURITY SQL CONNECTION to server " & dicSQLData("SQLServer") & " using login " & dicSQLData("DBID") & ".", LogTypeInfo
			sDSNRef = sDSNRef & ";User ID=" & dicSQLData("DBID") & ";Password=" & dicSQLData("DBPwd")
		End if


		' Connect to the database

		oLogging.CreateEntry "Connecting to SQL Server using connect string: " & sDSNref, LogTypeInfo
		On Error Resume Next
		oConn.Open sDSNref
		If Err then
			sMsg = Err.Description & " (" & Err.Number & ")"

			CreateEvent 41013, LogTypeError, "ZTI error opening SQL connection: " & sMsg

			iRetVal = Failure
			oLogging.CreateEntry "ZTI error opening SQL Connection: " & sMsg, LogTypeError
			For each objErr in oConn.Errors
				oLogging.CreateEntry "  ADO error: " & objErr.Description & " (Error #" & objErr.Number & "; Source: " & objErr.Source & "; SQL State: " & objErr.SQLState & "; NativeError: " & objErr.NativeError & ")", LogTypeError
			Next
			Err.Clear
			Set Connect = Nothing
			Exit Function
		End if
		On Error Goto 0

		oLogging.CreateEntry "Successfully opened connection to database.", LogTypeInfo



		' Return the connection to the caller

		Set Connect = oConn

	End Function


	Public Property Get Connection

		Set Connection = oConn

	End Property


	Public Function Query

		Dim oRS
		Dim sErrMsg, sSelect, sElement, sColumn, objTmp, bFoundColumn, bFirst
		Dim tmpValue, tmpArray, tmpClause, v, bClauseFirst, objErr
		Dim sMsg
		Dim bValueFound


		' Create ADO recordset object

		On Error Resume Next
		Set oRS = CreateObject("ADODB.Recordset")
		If Err then
			Set Query = Nothing
			oLogging.CreateEntry "ERROR - Unable to create ADODB.Recordset object, impossible to query SQL Server: " & Err.Description & " (" & Err.Number & ")", LogTypeError
			Exit Function
		End if
		On Error Goto 0


		' Build the SQL statement

		If dicSQLData("Table") <> "" then


			sSelect = "SELECT * FROM " & dicSQLData("Table") & " WHERE "
			bFirst = True
			For each sElement in dicSQLData("Parameters")

				sElement = UCase(trim(sElement))

				' Find the column ID to use

				sColumn = TranslateToColumnID(sElement)


				' Find the value to work with

				bValueFound = False
				If oEnvironment.ListItem(sElement).Count > 0 then
					Set tmpValue = oEnvironment.ListItem(sElement)
					For each v in tmpValue.Keys
						If v <> "" then
							bValueFound = true
							Exit For
						End if
					Next
				ElseIf oEnvironment.Item(sElement) <> "" then
					tmpValue = oEnvironment.Item(sElement)
					bValueFound = true
				Else
					tmpValue = ""
				End if

				If bValueFound then

					' Check if an AND/OR is needed

					If not bFirst then
						sSelect = sSelect & " " & dicSQLData("ParameterCondition") & " "
					Else
						bFirst = False
					End if


					' Handle it appropriately

					If IsObject(tmpValue) then  ' It must be a dictionary object
						tmpClause = sElement & " IN ("
						bClauseFirst = True
						For each v in tmpValue.Keys
							If not bClauseFirst then
								tmpClause = tmpClause & ","
							Else
								bClauseFirst = False
							End if
							tmpClause = tmpClause & "'" & v & "'"
						Next
						sSelect = sSelect & tmpClause & ")"
					Else
						sSelect = sSelect & sColumn & " = '" & tmpValue & "'"

					End if

				End if

			Next

			If bFirst then

				oLogging.CreateEntry "No parameters had non-blank values, adding dummy query clause to force no records.", LogTypeInfo
				sSelect = sSelect & "0=1"

			End if


			' See if we need to sort the results

			If UBound(dicSQLData("Order")) >= 0 then

				sSelect = sSelect & " ORDER BY "

				For each sElement in dicSQLData("Order")

					sElement = Trim(sElement)


					' Find the column ID to use

					sColumn = TranslateToColumnID(sElement)


					' Add the clause

					sSelect = sSelect & sColumn & ", "

				Next


				' Trim the last comma/space

				sSelect = Left(sSelect, Len(sSelect)-2)

			End if
					
		Else

			' Stored procedure to be added

			sSelect = "EXECUTE " & dicSQLData("StoredProcedure") & " "
			bFirst = True
			For each sElement in dicSQLData("Parameters")

				sElement = UCase(trim(sElement))

				' Find the value to work with

				If oEnvironment.ListItem(sElement).Count > 0 then
					Set tmpValue = oEnvironment.ListItem(sElement)
				ElseIf oEnvironment.Item(sElement) <> "" then
					tmpValue = oEnvironment.Item(sElement)
				Else
					oLogging.CreateEntry "No valid specified for parameter '" & sElement & "', stored procedure may return no records.", LogTypeInfo
					tmpValue = ""
				End if


				' Check if an AND is needed

				If not bFirst then
					sSelect = sSelect & ", "
				Else
					bFirst = False
				End if


				' Handle it appropriately

				If IsObject(tmpValue) then
					oLogging.CreateEntry "Only the first " & sElement & " value will be used in the stored procedure call.", LogTypeInfo
					tmpArray = tmpValue.Keys 
					sSelect = sSelect & "'" & tmpArray(0) & "'"
				Else
					sSelect = sSelect & "'" & tmpValue & "'"
				End if

			Next

		End if


		' Issue the SQL statement

		oLogging.CreateEntry "About to issue SQL statement: " & sSelect, LogTypeInfo
		On Error Resume Next
		oRS.Open sSelect, oConn, adOpenStatic, adLockReadOnly
		If Err then
			Set Query = Nothing
			oLogging.CreateEntry "ERROR - Opening Record Set (Error Number = " & Err.Number & ") (Error Description: " & Err.Description & ").", LogTypeError
			For each objErr in oConn.Errors
				oLogging.CreateEntry "  ADO error: " & objErr.Description & " (Error #" & objErr.Number & "; Source: " & objErr.Source & "; SQL State: " & objErr.SQLState & "; NativeError: " & objErr.NativeError & ")", LogTypeError
			Next
			oRS.Close
			Err.Clear
			Exit Function
		End if
		On Error Goto 0

		oLogging.CreateEntry "Successfully queried the database.", LogTypeInfo

		Set Query = oRS

	End Function

	Public Function TranslateToColumnID(sElement)

		Dim sColumn

		sColumn = oUtility.ReadIni(sIniFile, sSection, sElement)
		If sColumn = "" then
			sColumn = sElement
		End if

		TranslateToColumnID = sColumn

	End Function

End Class


Class WebService

	Private sIniFile
	Private sSection
	Private sURL
	Private arrParameters

	Private Sub Class_Initialize

		IniFile = oUtility.Arguments("inifile")

	End Sub


	Public Property Let IniFile(sIni)
		
		Dim sFoundIniFile
		Dim iRetVal


		' Figure out where the CustomSettings.ini file is
	
		sIniFile = sIni
		If Len(sIniFile) = 0 then
			iRetVal = oUtility.FindFile("CustomSettings.ini", sIniFile)
			If iRetVal <> Success then
				oLogging.CreateEntry "Unable to find CustomSettings.ini, rc = " & iRetVal, LogTypeError
				Exit Property
			End if
			oLogging.CreateEntry "Using DEFAULT VALUE: Ini file = " & sIniFile, LogTypeInfo
		Else
			If not oFSO.FileExists(sIniFile) then
				iRetVal = oUtility.FindFile(sIniFile, sFoundIniFile)
				If iRetVal = Success then
					sIniFile = sFoundIniFile
				End if
			End if
			oLogging.CreateEntry "Using COMMAND LINE ARG: Ini file = " & sIniFile, LogTypeInfo
		End if

		If Not oFSO.FileExists(sIniFile) then
			oLogging.CreateEntry "Specified INI file does not exist (" & sIniFile & ").", LogTypeError
			Exit Property
		End if 

	End Property

	Public Property Let SectionName(sSect)

		Dim sTmpVal


		sSection = oEnvironment.Substitute(sSect)
		oLogging.CreateEntry "CHECKING the [" & sSection & "] section", LogTypeInfo


		' Get the URL

		sURL = oUtility.ReadIni(sIniFile, sSection, "WebService")


		' Get "Parameters"

		sTmpVal = oUtility.ReadIni(sIniFile, sSection, "Parameters")
		If Len(sTmpVal) = 0 then
			oLogging.CreateEntry "No parameters to include in the web service call were specified", LogTypeInfo
			arrParameters = Array()
		Else
			arrParameters = Split(sTmpVal, ",")
		End if


	End Property


	Public Function Query

		Dim oHTTP
		Dim sEnvelope
		Dim sReturn
		Dim oReturn
		Dim oNode
		Dim sElement, sColumn
		Dim tmpValue, tmpArray


		Set oHTTP = CreateObject("MSXML2.ServerXMLHTTP")
		Set oReturn = CreateObject("MSXML2.DOMDocument")
		Set Query = oReturn


		' Build the envelope

		For each sElement in arrParameters

			sElement = Trim(sElement)


			' Find the column ID to use

			sColumn = TranslateToColumnID(sElement)
			sElement = UCase(sElement)


			' Find the value to work with

			If oEnvironment.ListItem(sElement).Count > 0 then
				Set tmpValue = oEnvironment.ListItem(sElement)
			ElseIf oEnvironment.Item(sElement) <> "" then
				tmpValue = oEnvironment.Item(sElement)
			Else
				oLogging.CreateEntry "No valid specified for parameter '" & sElement & "', web service results could be unpredictable.", LogTypeInfo
				tmpValue = ""
			End if


			' Handle it appropriately

			If IsObject(tmpValue) then
				oLogging.CreateEntry "Only the first " & sElement & " value will be used in the web service call.", LogTypeInfo
				tmpArray = tmpValue.Keys
				sEnvelope = sEnvelope & sColumn & "=" & tmpArray(0) & "&"
			Else
				sEnvelope = sEnvelope & sColumn & "=" & tmpValue & "&"
			End if

		Next

		If Len(sEnvelope) > 0 then
			sEnvelope = Left(sEnvelope, Len(sEnvelope) - 1)
		End if


		' Issue the web service call

		oLogging.CreateEntry "About to execute web service call to " & sURL & ": " & sEnvelope, LogTypeInfo

		oHTTP.open "POST", sURL, False
		oHTTP.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"

		On Error Resume Next
		oHTTP.send sEnvelope
		If Err then
			oLogging.CreateEntry "Error executing web service " & sURL & ": " & Err.Description & " (" & Err.Number & ")", LogTypeError
			Set Query = Nothing
			Exit Function
		End if
		If oHTTP.status = 200 then
			oLogging.CreateEntry "Response from web service: " & oHTTP.status & " " & oHTTP.StatusText, LogTypeInfo
		Else
			oLogging.CreateEntry "Unexpected response from web service: " &	 oHTTP.status & " " & oHTTP.StatusText & vbCrLf & oHTTP.responseText, LogTypeError
			Set Query = Nothing
			Exit Function
		End if


		' Process the results

		oReturn.loadXML oHTTP.responseText

		oLogging.CreateEntry "Successfully executed the web service.", LogTypeInfo

	End Function

	Public Function TranslateToColumnID(sElement)

		Dim sColumn

		sColumn = oUtility.ReadIni(sIniFile, sSection, UCase(sElement))
		If sColumn = "" then
			sColumn = sElement
		End if

		TranslateToColumnID = sColumn

	End Function

End Class
